/* 
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

using System;
using System.Collections.Generic;
using Lucene.Net.Support;
using IndexOutput = Lucene.Net.Store.IndexOutput;
using Similarity = Lucene.Net.Search.Similarity;

namespace Lucene.Net.Index
{
    
    // TODO FI: norms could actually be stored as doc store
    
    /// <summary>Writes norms.  Each thread X field accumulates the norms
    /// for the doc/fields it saw, then the flush method below
    /// merges all of these together into a single _X.nrm file.
    /// </summary>
    
    sealed class NormsWriter : InvertedDocEndConsumer
    {
        
        private static readonly byte defaultNorm;
        private FieldInfos fieldInfos;
        public override InvertedDocEndConsumerPerThread AddThread(DocInverterPerThread docInverterPerThread)
        {
            return new NormsWriterPerThread(docInverterPerThread, this);
        }
        
        public override void  Abort()
        {
        }
        
        // We only write the _X.nrm file at flush
        internal void  Files(ICollection<string> files)
        {
        }
        
        internal override void  SetFieldInfos(FieldInfos fieldInfos)
        {
            this.fieldInfos = fieldInfos;
        }
        
        /// <summary>Produce _X.nrm if any document had a field with norms
        /// not disabled 
        /// </summary>
        public override void Flush(IDictionary<InvertedDocEndConsumerPerThread,ICollection<InvertedDocEndConsumerPerField>> threadsAndFields, SegmentWriteState state)
        {

            IDictionary<FieldInfo, IList<NormsWriterPerField>> byField = new HashMap<FieldInfo, IList<NormsWriterPerField>>();
            
            // Typically, each thread will have encountered the same
            // field.  So first we collate by field, ie, all
            // per-thread field instances that correspond to the
            // same FieldInfo
            foreach(var entry in threadsAndFields)
            {
                ICollection<InvertedDocEndConsumerPerField> fields = entry.Value;
                IEnumerator<InvertedDocEndConsumerPerField> fieldsIt = fields.GetEnumerator();
                var fieldsToRemove = new HashSet<NormsWriterPerField>();
                while (fieldsIt.MoveNext())
                {
                    NormsWriterPerField perField = (NormsWriterPerField) fieldsIt.Current;
                    
                    if (perField.upto > 0)
                    {
                        // It has some norms
                        IList<NormsWriterPerField> l = byField[perField.fieldInfo];
                        if (l == null)
                        {
                            l = new List<NormsWriterPerField>();
                            byField[perField.fieldInfo] = l;
                        }
                        l.Add(perField);
                    }
                    // Remove this field since we haven't seen it
                    // since the previous flush
                    else
                    {
                        fieldsToRemove.Add(perField);
                    }
                }
                foreach (var field in fieldsToRemove)
                {
                    fields.Remove(field);
                }
            }
            
            System.String normsFileName = state.segmentName + "." + IndexFileNames.NORMS_EXTENSION;
            state.flushedFiles.Add(normsFileName);
            IndexOutput normsOut = state.directory.CreateOutput(normsFileName);
            
            try
            {
                normsOut.WriteBytes(SegmentMerger.NORMS_HEADER, 0, SegmentMerger.NORMS_HEADER.Length);
                
                int numField = fieldInfos.Size();
                
                int normCount = 0;
                
                for (int fieldNumber = 0; fieldNumber < numField; fieldNumber++)
                {
                    
                    FieldInfo fieldInfo = fieldInfos.FieldInfo(fieldNumber);
                    
                    IList<NormsWriterPerField> toMerge = byField[fieldInfo];
                    int upto = 0;
                    if (toMerge != null)
                    {
                        
                        int numFields = toMerge.Count;
                        
                        normCount++;
                        
                        NormsWriterPerField[] fields = new NormsWriterPerField[numFields];
                        int[] uptos = new int[numFields];
                        
                        for (int j = 0; j < numFields; j++)
                            fields[j] = toMerge[j];
                        
                        int numLeft = numFields;
                        
                        while (numLeft > 0)
                        {
                            
                            System.Diagnostics.Debug.Assert(uptos [0] < fields [0].docIDs.Length, " uptos[0]=" + uptos [0] + " len=" +(fields [0].docIDs.Length));
                            
                            int minLoc = 0;
                            int minDocID = fields[0].docIDs[uptos[0]];
                            
                            for (int j = 1; j < numLeft; j++)
                            {
                                int docID = fields[j].docIDs[uptos[j]];
                                if (docID < minDocID)
                                {
                                    minDocID = docID;
                                    minLoc = j;
                                }
                            }
                            
                            System.Diagnostics.Debug.Assert(minDocID < state.numDocs);
                            
                            // Fill hole
                            for (; upto < minDocID; upto++)
                                normsOut.WriteByte(defaultNorm);
                            
                            normsOut.WriteByte(fields[minLoc].norms[uptos[minLoc]]);
                            (uptos[minLoc])++;
                            upto++;
                            
                            if (uptos[minLoc] == fields[minLoc].upto)
                            {
                                fields[minLoc].Reset();
                                if (minLoc != numLeft - 1)
                                {
                                    fields[minLoc] = fields[numLeft - 1];
                                    uptos[minLoc] = uptos[numLeft - 1];
                                }
                                numLeft--;
                            }
                        }
                        
                        // Fill final hole with defaultNorm
                        for (; upto < state.numDocs; upto++)
                            normsOut.WriteByte(defaultNorm);
                    }
                    else if (fieldInfo.isIndexed && !fieldInfo.omitNorms)
                    {
                        normCount++;
                        // Fill entire field with default norm:
                        for (; upto < state.numDocs; upto++)
                            normsOut.WriteByte(defaultNorm);
                    }
                    
                    System.Diagnostics.Debug.Assert(4 + normCount * state.numDocs == normsOut.FilePointer, ".nrm file size mismatch: expected=" +(4 + normCount * state.numDocs) + " actual=" + normsOut.FilePointer);
                }
            }
            finally
            {
                normsOut.Close();
            }
        }
        
        internal override void  CloseDocStore(SegmentWriteState state)
        {
        }
        static NormsWriter()
        {
            defaultNorm = Similarity.EncodeNorm(1.0f);
        }
    }
}